﻿using LiteStep.Currencies;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MoreLinq;
using LiteStep.Exchanges;

namespace LiteStep.Conversions
{
	public class SimpleConversionManager : IConversionManager
	{
		private IDictionary<ICurrencyType, IDictionary<ICurrencyType, IDictionary<IExchangeType, IConversion>>> Map { get; set; }

		public SimpleConversionManager()
		{
			this.Map = new Dictionary<ICurrencyType, IDictionary<ICurrencyType, IDictionary<IExchangeType, IConversion>>>();
		}

		public IEnumerable<IConversion<TFrom, TTo, IExchange<IExchange>>> Get<TFrom, TTo>()
			where TFrom : ICurrency
			where TTo : ICurrency
		{
            lock (this)
            {
                var fromToMap = this.DoGet<TFrom, TTo>();
			    return (from mapping
				        in fromToMap
					    //Safe, we control the puts
					    select (IConversion<TFrom, TTo, IExchange<IExchange>>)mapping.Value).ToList();
            }
			
		}

		public IConversion<TFrom, TTo, TOn> Get<TFrom, TTo, TOn>()
			where TFrom : ICurrency
			where TTo : ICurrency
			where TOn : IExchange
		{
            lock (this)
            {
                return this.DoGet<TFrom, TTo, TOn>();
            }
		}


		public IConversion<TFrom, TTo, IExchange<IExchange>> GetBest<TFrom, TTo>()
			where TFrom : ICurrency
			where TTo : ICurrency
		{
            lock (this)
            {
                return this.Get<TFrom, TTo>().MaxBy(c => c.ConversionRatio);
            }
		}


		public void Set<TFrom, TTo, TOn>(IConversion<TFrom, TTo, TOn> conversion)
			where TFrom : ICurrency
			where TTo : ICurrency
			where TOn : IExchange
		{
            lock (this)
            {
                if (conversion == null)
				    throw new ArgumentNullException("conversion", "conversion cannot be null");

			    var currentConversion = this.DoGet<TFrom, TTo, TOn>();
			    var fromType = new CurrencyType<TFrom>();
			    var toType = new CurrencyType<TTo>();
			    var onType = new ExchangeType<TOn>();
			    if (currentConversion == null || currentConversion.Timestamp <= conversion.Timestamp)
			    {
				    this.EnsureMap<TFrom, TTo>();
				    if (!this.Map[fromType][toType].ContainsKey(onType))
				    {
					    this.Map[fromType][toType].Add(onType, conversion);
				    }
				    else if (this.Map[fromType][toType][onType] == null)
				    {
					    this.Map[fromType][toType][onType] = conversion;
				    }
			    }
            }
		}

		private IDictionary<ICurrencyType, IDictionary<IExchangeType, IConversion>> DoGet<TFrom>()
			where TFrom : ICurrency
		{
			var fromType = new CurrencyType<TFrom>();

			if (this.Map == null || !this.Map.ContainsKey(fromType)) { return null; }

			return this.Map[fromType];
		}

		private IDictionary<IExchangeType, IConversion> DoGet<TFrom, TTo>()
			where TFrom : ICurrency
			where TTo : ICurrency
		{
			var toType = new CurrencyType<TTo>();
			var fromMap = this.DoGet<TFrom>();

			if (fromMap == null || !fromMap.ContainsKey(toType)) { return null; }

			return fromMap[toType];
		}

		private IConversion<TFrom, TTo, TOn> DoGet<TFrom, TTo, TOn>()
			where TFrom : ICurrency
			where TTo : ICurrency
			where TOn : IExchange
		{
			var exchangeType = new ExchangeType<TOn>();
			var fromToMap = this.DoGet<TFrom, TTo>();

			if (fromToMap == null || !fromToMap.ContainsKey(exchangeType)) { return null; }

			//Safe, we control the puts
			return (IConversion<TFrom, TTo, TOn>)fromToMap[exchangeType];
		}

		private void EnsureMap()
		{
			if (this.Map == null)
			{
				this.Map = new Dictionary<ICurrencyType, IDictionary<ICurrencyType, IDictionary<IExchangeType, IConversion>>>();
			}
		}

		private void EnsureMap<TFrom>()
			where TFrom : ICurrency
		{
			this.EnsureMap();

			var fromType = new CurrencyType<TFrom>();
			if (!this.Map.ContainsKey(fromType))
			{
				this.Map.Add(fromType, new Dictionary<ICurrencyType, IDictionary<IExchangeType, IConversion>>());
			}
			else if (this.Map[fromType] == null)
			{
				this.Map[fromType] = new Dictionary<ICurrencyType, IDictionary<IExchangeType, IConversion>>();
			}
		}

		private void EnsureMap<TFrom, TTo>()
			where TFrom : ICurrency
			where TTo : ICurrency
		{
			this.EnsureMap<TFrom>();

			var fromType = new CurrencyType<TFrom>();
			var toType = new CurrencyType<TTo>();
			if (!this.Map[fromType].ContainsKey(toType))
			{
				this.Map[fromType].Add(toType, new Dictionary<IExchangeType, IConversion>());
			}
			else if (this.Map[fromType][toType] == null)
			{
				this.Map[fromType][toType] = new Dictionary<IExchangeType, IConversion>();
			}
		}
	}
}
